﻿using System;
using System.ComponentModel;
using System.Reflection;
using System.Web.UI;
using System.Web.UI.WebControls;


namespace CodeAffinity.FormAbstraction
{
    /// <summary>
    /// centralizes load/save between a service data object and a webform
    /// </summary>
    public class FormObjectDataInterchange
    {
        private DataObjectPage ContainingPage { get; set; }
        private Type DataObjectType { get; set; }
        private DataClassBase CurrentData { get; set; }
        /// <summary>
        /// retrieves values from form & modifies the data object
        /// </summary>
        public void FormToObject(DataObjectPage page)
        {
            ContainingPage = page;

            for (int i = 0; i < ContainingPage.Session.Count; i++)
            {
                if (ContainingPage.Session[i] is DataClassBase)
                {
                    CurrentData = (DataClassBase)ContainingPage.Session[i];
                    DataObjectType = CurrentData.GetType();
                    PropertyInfo[] properties = DataObjectType.GetProperties();

                    foreach (PropertyInfo pi in properties)
                        if (pi.CanWrite)
                        {
                            Control c = FindControl(DataObjectType, pi.Name);
                            if (c != null)
                                AssignObjectValue(c, pi);
                        }
                }
            }
        }

        /// <summary>
        /// takes data object values and loads them to the page supplied
        /// </summary>
        /// <param name="page">the page requested to load</param>
        public void ObjectToForm(DataObjectPage page)
        {
            ContainingPage = page;

            for (int i = 0; i < ContainingPage.Session.Count; i++)
            {
                if (ContainingPage.Session[i] is DataClassBase)
                {
                    CurrentData = (DataClassBase)ContainingPage.Session[i];
                    DataObjectType = CurrentData.GetType();
                    PropertyInfo[] properties = DataObjectType.GetProperties();

                    foreach (PropertyInfo pi in properties)
                        if (pi.CanRead)
                        {
                            Control c = FindControl(DataObjectType, pi.Name);
                            if (c != null)
                                AssignControlValue(c, pi);
                        }
                }
            }
        }

        /// <summary>
        /// Assigns value to a control
        /// </summary>
        /// <param name="c">control to assign to</param>
        /// <param name="prop">property where the value is stored</param>
        void AssignObjectValue(Control c, PropertyInfo prop)
        {
            object val = GetControlValue(c);

            if (val is bool)
                prop.SetValue(CurrentData, val, null);
            else
            {
                if (val != String.Empty)
                {
                    TypeConverter converter = TypeDescriptor.GetConverter(prop.PropertyType);
                    prop.SetValue(CurrentData, converter.ConvertFrom(val), null);
                }
                else if (prop.PropertyType.Name.Equals("String"))
                    prop.SetValue(CurrentData, "", null);
            }
        }

        /// <summary>
        /// Gets the value from the control in an appropriate way for it's concrete type
        /// </summary>
        /// <param name="c">the control that has a value we are interested in</param>
        /// <returns>the value contained by the control supplied</returns>
        object GetControlValue(Control c)
        {
            object r = new object();
            if (c is TextBox)
                r = ((TextBox)c).Text;
            if (c is Label)
                r = ((Label)c).Text;
            if (c is ListControl)
                r = ((ListControl)c).SelectedValue;
            if (c is CheckBox)
                r = ((CheckBox)c).Checked;
            return r;
        }

        /// <summary>
        /// inspects control and assigns value appropriate to its concrete type
        /// </summary>
        /// <param name="c">a control that we are interested in assigning value to</param>
        /// <param name="pi">the object property that currently has a value we want to assign</param>
        void AssignControlValue(Control c, PropertyInfo pi)
        {
            if (!ContainingPage.DisableLoad && pi.GetValue(CurrentData, null) != null)
            {
                if (c is TextBox)
                {
                    TextBox tb = (TextBox)c;
                    if (tb.Text != String.Empty)
                        throw new DataReloadAttempted(DataObjectType.Name, tb.ID);
                    tb.Text = pi.GetValue(CurrentData, null).ToString();
                }

                if (c is Label)
                {
                    Label l = (Label)c;
                    if (l.Text != String.Empty)
                        throw new DataReloadAttempted(DataObjectType.Name, l.ID);
                    l.Text = pi.GetValue(CurrentData, null).ToString();
                }

                if (c is CheckBox)
                {
                    CheckBox cb = (CheckBox)c;
                    if (cb.Checked == true)
                        throw new DataReloadAttempted(DataObjectType.Name, cb.ID);
                    cb.Checked = (bool)pi.GetValue(CurrentData, null);
                }

            }
            if (c is HyperLink)
            {
                (c as HyperLink).Text = pi.GetValue(CurrentData, null).ToString();
            }
            if (c is ListControl)
            {
                ListControl lctrl = ((ListControl)c);
                lctrl.Items.AddRange(GetListForListControl(pi));


                string currVal = pi.GetValue(CurrentData, null) == null ? String.Empty : pi.GetValue(CurrentData, null).ToString();
                if (!ContainingPage.DisableLoad && currVal != String.Empty)
                    lctrl.SelectedValue = currVal;
            }
        }
        
        /// <summary>
        /// Find the control by naming convention (objectName_propertyName or propertyName)
        /// </summary>
        /// <param name="currentType">The type that the property comes from</param>
        /// <param name="propertyName">the name of the property we are trying to find on a form</param>
        /// <returns>a control that matches</returns>
        Control FindControl(Type currentType, string propertyName)
        {
            Control c = 
                ContainingPage.FindControlRecursive(ContainingPage.Page, propertyName);

            if (c == null)
                c = ContainingPage.FindControlRecursive(ContainingPage.Page,  currentType.Name + "_" + propertyName);
            
            return c;
        }

        /// <summary>
        /// gets a list to load ListControls by executing the datasource in a foreign key attribute
        /// </summary>
        /// <param name="pInfo">the property that has a foriegn key attribute and a ListControl associated</param>
        /// <returns>ListItem[] of results</returns>
        ListItem[] GetListForListControl(PropertyInfo pInfo)
        {
            ListItem[] items = null;

            object[] attrs = pInfo.GetCustomAttributes(false);

            if (attrs.Length == 0)
                throw new ListControlNotValid(DataObjectType.Name +"."+ pInfo.Name);

            foreach (ForeignKeyPropertyAttribute attr in attrs)
            {
                //we have a type from the attribute, we need an instance to do some work
                BusinessBase bb = (BusinessBase)Activator.CreateInstance(attr.DataClassType);

                //go get the method indicated by the attribute
                MethodInfo meth = attr.DataClassType.GetMethod(attr.DataMethodName);
                
                //call the method on the instance we created above and get the product
                DataClassBase[] dataObjs = (DataClassBase[])meth.Invoke(bb, null);


                //for each object returned, go get the values we need and build them out
                //to a ListItem[] which the ListItem.Items.AddRange() expects (see calling func)
                items = new ListItem[dataObjs.Length];

                for (int i = 0; i < dataObjs.Length; i++)
                {
                    DataClassBase dataItem = dataObjs[i];

                    ListItem li = new ListItem();
                    Type t = dataItem.GetType();

                    li.Text = t.GetProperty(attr.DataTextField).GetValue(dataItem, null).ToString();
                    li.Value = t.GetProperty(attr.DataValueField).GetValue(dataItem, null).ToString();

                    items[i] = li;
                }
            }
            return items;
        }

    }
}
